//-----------------------------------------------------------------------
// <copyright file="ClassMockAcceptanceTest.cs" company="NMock2">
//
//   http://www.sourceforge.net/projects/NMock2
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
// This is the easiest way to ignore StyleCop rules on this file, even if we shouldn't use this tag:
// <auto-generated />
//-----------------------------------------------------------------------
namespace NMock2.AcceptanceTests
{
    using System;
    using System.Collections;
    using NUnit.Framework;

    [TestFixture]
    public class ClassMockAcceptanceTest : AcceptanceTestBase
    {
        [Test, Class]
        public void CanSetExpectationOnVirtualMethod()
        {
            SampleClassWithVirtualAndNonVirtualMethods mock = Mocks.NewMock<SampleClassWithVirtualAndNonVirtualMethods>();
            
            // Virtual target method
            Expect.Once.On(mock).Method("Add").With(1, 2).Will(Return.Value(10));
            Assert.AreEqual(10, mock.Add(1, 2));
        }

        [Test, Class]
        public void CanSetExpectationOnAbstractMethod()
        {
            SampleClassWithVirtualAndNonVirtualMethods mock = Mocks.NewMock<SampleClassWithVirtualAndNonVirtualMethods>();
            
            // Abstract target method
            Expect.Once.On(mock).Method("Add").With(1.1m, 2.1m).Will(Return.Value(10));
            Assert.AreEqual(10, mock.Add(1.1m, 2.1m));
        }

		//, "mock object myMock has a method matching Subtract, but it is not virtual or abstract"
        [Test, Class, ExpectedException(typeof(ArgumentException))]
        public void SettingExpectationOnNonVirtualMethodThrowsArgumentException()
        {
            SampleClassWithVirtualAndNonVirtualMethods mock = Mocks.NewNamedMock<SampleClassWithVirtualAndNonVirtualMethods>("myMock");

            // The target method is non-virtual, so we expect an exception.
            // (we use 'Never' here to avoid an undesired failure in teardown).
            Expect.Never.On(mock).Method("Subtract").With(2, 1).Will(Return.Value(10));
        }

        // This is a problem, as we can't easily use args to identify overload
        // as expectation is defined, and we can't do it at execution time, because
        // call will not be intercepted (proxy is only for virtual members).
        [Test, Class, ExpectedException(typeof(ArgumentException))]
        [Ignore("We can't identify overloaded non-virtual members by arguments.")]
        public void SettingExpectationOnNonVirtualOverloadOfVirtualMethodThrowsArgumentException()
        {
            SampleClassWithVirtualAndNonVirtualMethods mock = Mocks.NewMock<SampleClassWithVirtualAndNonVirtualMethods>();

            // The target method is non-virtual, so we expect an exception.
            Expect.Once.On(mock).Method("Add").With(1, 2, 3).Will(Return.Value(10));
        }

		//, "mock object myMock has a method matching SomeMethod, but it is not virtual or abstract"
        [Test, Class, ExpectedException(typeof(ArgumentException))]
        public void SettingExpectationOnNonVirtualImplOfInterfaceMethodThrowsArgumentException()
        {
            SampleInterfaceImpl mock = Mocks.NewNamedMock<SampleInterfaceImpl>("myMock");

            // The target method is non-virtual, so we expect an exception.
            Expect.Never.On(mock).Method("SomeMethod");
        }

		//, "mock object myMock has a method matching SomeGenericMethod, but it is not virtual or abstract"
        [Test, Class, ExpectedException(typeof(ArgumentException))]
        public void SettingExpectationOnNonVirtualImplOfInterfaceGenericMethodThrowsArgumentException()
        {
            SampleInterfaceImpl mock = Mocks.NewNamedMock<SampleInterfaceImpl>("myMock");

            // The target method is non-virtual, so we expect an exception.
            Expect.Never.On(mock).Method("SomeGenericMethod", typeof(int));
        }

		//, "mock object myMock has a method matching SomeOtherMethod, but it is not virtual or abstract"
        [Test, Class, ExpectedException(typeof(ArgumentException))]
        public void SettingExpectationOnInheritedNonVirtualImplOfInterfaceMethodThrowsArgumentException()
        {
            SampleInterfaceImpl mock = Mocks.NewNamedMock<SampleInterfaceImpl>("myMock");

            // The target method is non-virtual, so we expect an exception.
            Expect.Never.On(mock).Method("SomeOtherMethod");
        }

        [Test, Class]
        public void CanSetExpectationOnMethodWhenAtLeastOneMatchedOverloadIsVirtualOrAbstract()
        {
            SampleClassWithVirtualAndNonVirtualMethods mock = Mocks.NewMock<SampleClassWithVirtualAndNonVirtualMethods>();
            
            // Three possible matches here...
            Expect.Exactly(2).On(mock).Method("Add").Will(Return.Value(10));
            Assert.AreEqual(10, mock.Add(1, 2), "Virtual method expectation failed");
            Assert.AreEqual(10, mock.Add(1.1m, 2.1m), "Abstract method expectation failed");
            Assert.AreEqual(6, mock.Add(1, 2, 3), "Expected call to non-virtual method to go to implementation");
        }

        [Test, Class]
        public void CanHaveClassWithIMockObjectMembers()
        {
            SampleClassWithIMockObjectMembers mock = Mocks.NewMock<SampleClassWithIMockObjectMembers>();
            Expect.Once.On(mock).Method("Multiply").Will(Return.Value(133));
            int result = mock.Multiply(12, 12);
            //// even if 12 by 12 is 144, we mocked the method with 133:
            Assert.AreEqual(133, result, "Mock wasn't created successful.");
        }

        [Test, Class]
        public void CanCallNonMockedMethodOfTransparentClassMock()
        {
            SampleClass mock = Mocks.NewMock<SampleClass>(MockStyle.Transparent);
            Expect.Once.On(mock).Method("DoAdd").Will(Return.Value(7));
            Assert.AreEqual(7, mock.Add(1, 2));

            // Call to non-mocked method "Divide"
            decimal remainder;
            decimal divideResult = mock.Divide(10, 2, out remainder);
            Assert.AreEqual(5m, divideResult, "Expected 5 as result of calling Divide (10/2).");
        }

        [Test, Class]
        public void CanSetExpectationOnMethodOfTransparentClassMock()
        {
            SampleClass mock = Mocks.NewMock<SampleClass>(MockStyle.Transparent);

            Expect.Once.On(mock).Method("DoAdd").Will(Return.Value(7));

            Assert.AreEqual(7, mock.Add(1, 2));
        }

        [Test, Class]
        public void CanSetExpectationOnAbstractMethodOfClassMock()
        {
            SampleAbstractClass mock = Mocks.NewMock<SampleAbstractClass>();

            Expect.Once.On(mock).Method("Add").Will(Return.Value(7));

            Assert.AreEqual(7, mock.Add(1, 2));
        }

        [Test, Class]
        public void LocalCallsWithinMockedClassCanSatisfyExpectations()
        {
            SampleClass mock = Mocks.NewMock<SampleClass>();

            Expect.Once.On(mock).Method("DoAdd").Will(Return.Value(7)); // Should be called from Add()

            Assert.AreEqual(7, mock.Add(1, 2));
        }

        [Test, Class]
        public void CanSetExpectationOnMethodOnMockedGenericClass()
        {
            SampleGenericClass<string> mock = Mocks.NewMock<SampleGenericClass<string>>();

            Expect.Once.On(mock).Method("GetDefault").Will(Return.Value("ABC"));

            Assert.AreEqual("ABC", mock.GetDefault());
        }

        [Test, Class]
        public void CanSetExpectationOnGenericMethodOnMockedClass()
        {
            SampleClassWithGenericMethods mock = Mocks.NewMock<SampleClassWithGenericMethods>();

            Expect.Once.On(mock).Method("GetStringValue").Will(Return.Value("ABC"));

            Assert.AreEqual("ABC", mock.GetStringValue("XYZ"));
        }

        [Test, Class]
        public void CanSetExpectationOnGenericMethodWithConstraintOnMockedClass()
        {
            SampleClassWithGenericMethods mock = Mocks.NewMock<SampleClassWithGenericMethods>();

            Expect.Once.On(mock).Method("GetCount").Will(Return.Value(3));

            Assert.AreEqual(3, mock.GetCount(new int[5]));
        }

        [Test, Class]
        public void CanSetExpectationOnMethodOnSuperClassOfMockedClass()
        {
            SampleSubClass mock = Mocks.NewMock<SampleSubClass>();

            Expect.Once.On(mock).Method("DoAdd").Will(Return.Value(5));

            Assert.AreEqual(10, mock.AddThenDouble(1, 2));
        }

        [Test, Class]
        public void CanSetExpectationOnMethodWithOutParameterOnMockedClass()
        {
            SampleClass mock = Mocks.NewMock<SampleClass>();

            Expect.Once.On(mock).Method("Divide").Will(Return.Value(3), Return.OutValue("remainder", 15m));

            decimal remainder;
            mock.Divide(7, 2, out remainder);

            Assert.AreEqual(15m, remainder);
        }

        [Test, Class]
        public void CanSetExpectationOnOverriddenObjectMembersOnClassMock()
        {
            SampleClassWithObjectOverrides mock = Mocks.NewMock<SampleClassWithObjectOverrides>();

            Expect.Once.On(mock).Method("ToString").Will(Return.Value("ABC"));
            Expect.Once.On(mock).Method("Equals").Will(Return.Value(false));
            Expect.Once.On(mock).Method("GetHashCode").Will(Return.Value(17));

            Assert.AreEqual("ABC", mock.ToString(), "unexpected ToString() value");
            Assert.IsFalse(mock.Equals(mock), "unexpected Equals() value");
            Assert.AreEqual(17, mock.GetHashCode(), "unexpected GetHashCode() value");
        }

        #region Testing classes

        public abstract class SampleClassWithVirtualAndNonVirtualMethods
        {
            public int Subtract(int x, int y) // non-virtual
            {
                return x - y;
            }

            public virtual int Add(int x, int y) // virtual, overloaded
            {
                return x + y;
            }

            public int Add(int x, int y, int z) // non-virtual, overloaded
            {
                return x + y + z;
            }

            public abstract int Add(decimal x, decimal y); // abstract, overloaded
        }

        public class SampleClassWithIMockObjectMembers
        {
            public virtual int Multiply(int a, int b)
            {
                return a*b;
            }

            #region IMockObject Members

            public string MockName
            {
                get { throw new Exception("The method or operation is not implemented."); }
            }

            public bool HasMethodMatching(Matcher methodMatcher)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public void AddExpectation(IExpectation expectation)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public void AddEventHandler(string eventName, Delegate handler)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public void RemoveEventHandler(string eventName, Delegate handler)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public void RaiseEvent(string eventName, params object[] args)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            #endregion
        }

        public abstract class SampleAbstractClass
        {
            public abstract int Add(int a, int b);
        }

        public class SampleClass
        {
            public int Add(int a, int b)
            {
                return this.DoAdd(a, b);
            }

            protected virtual int DoAdd(int a, int b)
            {
                return a + b;
            }

            public virtual int Divide(int a, int b, out decimal remainder)
            {
                remainder = a % b;

                return a / b;
            }
        }

        public class SampleSubClass : SampleClass
        {
            public int AddThenDouble(int a, int b)
            {
                return DoAdd(a, b) * 2;
            }
        }

        public class SampleClassWithObjectOverrides
        {
            public override string ToString()
            {
                return base.ToString();
            }

            public override bool Equals(object obj)
            {
                return base.Equals(obj);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        public class SampleGenericClass<T>
        {
            public virtual T GetDefault()
            {
                return default(T);
            }
        }

        public class SampleClassWithGenericMethods
        {
            public virtual string GetStringValue<T>(T input)
            {
                return input.ToString();
            }

            public virtual int GetCount<T>(T list) where T : IList
            {
                return list.Count;
            }
        }

        public interface ISampleInterface
        {
            int SomeMethod();
            int SomeOtherMethod();
            int SomeGenericMethod<T>(T input);
        }

        public class SampleInterfaceImpl : SampleInterfaceImplSuperClass, ISampleInterface
        {
            // non-virtual impl of interface method
            public int SomeMethod()
            {
                return 7;
            }

            // non-virtual impl of interface generic method
            public int SomeGenericMethod<T>(T input)
            {
                return 1;
            }
        }

        public class SampleInterfaceImplSuperClass
        {
            // non-virtual impl of interface method declared
            // at a differnt level of inheritance heirarchy
            public int SomeOtherMethod()
            {
                return 3;
            }
        }

        #endregion
    }
}
